//
//  CoreDataStack.m
//  SYCoreDataProject
//
//  Created by gambler. on 2019/4/22.
//  Copyright © 2019 gambler. All rights reserved.
//

#import "CoreDataStack.h"
static CoreDataStack *stack = nil;
#define CoreDataShare [CoreDataStack shareStack]
#define DBURL [[self getDocumentUrlPath] URLByAppendingPathComponent:@"sql.db" isDirectory:YES]
@interface CoreDataStack()
@end

@implementation CoreDataStack
#pragma mark - Core Data stack
@synthesize persistentContainer = _persistentContainer;

+(instancetype)shareStack
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        stack = [[CoreDataStack alloc] init];
    });
    return stack;
}


/**
 获取文件位置
 
 @return 文件位置
 */
-(NSURL*)getDocumentUrlPath
{
    return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
}


/**
 CoreData 增
 
 @param dict 需要新增模型的键值
 @param success 成功
 @param fail 失败
 */
-(void)inserNewEntity:(NSDictionary*)dict newObjectForEntityForName:(NSString*)entityForName success:(void(^)(NSManagedObject*))success fail:(void(^)(NSError*))fail
{
    if (!dict || dict.count == 0) return;
    // 通过传入上下文和实体名称，创建一个名称对应的实体对象（相当于数据库一组数据，其中含有多个字段）
    NSManagedObject *entity = [NSEntityDescription insertNewObjectForEntityForName:entityForName inManagedObjectContext:self.managedObjectContext];
    
    for (NSString* key in dict.allKeys) {
        [entity setValue:dict[key] forKey:key];
    }
    NSError *error = nil;
    BOOL result = [self.managedObjectContext save:&error];
    if (!result) {
        NSLog(@"添加数据失败：%@",error);
        if (fail) {
            fail(error);
        }
    }
    else
    {
        NSLog(@"添加数据成功");
        if (success) {
            success(entity);
        }
    }
}


/**
 CoreData 删
 
 @param object 需要删除的实体
 @param success 成功
 @param fail 失败
 */
-(void)delectEntity:(NSArray<NSManagedObject*>*)object success:(void(^)(void))success fail:(void(^)(NSError*))fail
{
    for (NSManagedObject *managedObject in object) {
        [self.managedObjectContext deleteObject:managedObject];
    }
    
    NSError *error = nil;
    [self.managedObjectContext save:&error];
    if (error) {
        NSLog(@"删除数据失败：%@",error);
        if (fail) {
            fail(error);
        }
    }
    else
    {
        NSLog(@"删除数据成功");
        if (success) {
            success();
        }
    }
}

/**
 CoreData 查
 
 @param selectKeys 数组高级排序（数组里存放实体中的key，顺序按自己需要的先后存放即可），实体key来排序
 @param isAscending 升降序
 @param filterString 谓语
 @param success 成功
 @param fail 失败
 */
-(void)selectEntity:(NSArray* __nullable)selectKeys ObjectForEntityForName:(NSString* __nullable)entityForName ascending:(BOOL)isAscending filterString:(NSString* __nullable)filterString success:(void(^)(NSAsynchronousFetchResult*))success fail:(void(^)(NSError*))fail
{
    //    1.初始化一个查询请求
    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    //    2.设置要查询的实体
    NSEntityDescription *desc = [NSEntityDescription entityForName:entityForName inManagedObjectContext:self.managedObjectContext];
    request.entity = desc;
    //    3.设置查询结果排序
    if (selectKeys && selectKeys.count > 0) {
        NSMutableArray *arrary = [NSMutableArray array];
        for (NSString* key in selectKeys) {
            /**
             *  设置查询结果排序
             *  sequenceKey:根据某个属性（相当于数据库某个字段）来排序
             *  isAscending:是否升序
             */
            NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:key ascending:isAscending];
            [arrary addObject:sort];
        }
        
        if (arrary.count > 0) {
            request.sortDescriptors = arrary;// 可以添加多个排序描述器，然后按顺序放进数组即可
        }
    }
    
    //    4.设置添加过滤
    if (filterString) { //如果设置了过滤语句，谓语
        NSPredicate *predicate = [NSPredicate predicateWithFormat:filterString];
        request.predicate = predicate;
    }
    
    //    5.执行请求
    NSError *error = nil;
    NSAsynchronousFetchResult *objs = [self.managedObjectContext executeRequest:request error:&error];
    if (error) {
        if (fail) {
            fail(error);
        }
    }
    else
    {
        if (success) {
            success(objs);
        }
    }
}

/**
 CoreData 更新
 
 @param success 成功
 @param fail 失败
 */
-(void)updateEntity:(void(^)(void))success fail:(void(^)(NSError*))fail
{
    NSError *error = nil;
    [self.managedObjectContext save:&error];
    
    if (error) {
        NSLog(@"更新失败：%@",error);
        if (fail)
        {
            fail(error);
            
        }
        
    } else
    {
        if (success)
        {
            success();
            
        }
    }
}



#pragma mark - 懒加载
- (NSPersistentContainer *)persistentContainer {
    // The persistent container for the application. This implementation creates and returns a container, having loaded the store for the application to it.
    @synchronized (self) {
        if (_persistentContainer == nil) {
            _persistentContainer = [[NSPersistentContainer alloc] initWithName:@"SYCoreDataProject"];
            [_persistentContainer loadPersistentStoresWithCompletionHandler:^(NSPersistentStoreDescription *storeDescription, NSError *error) {
                if (error != nil) {
                    // Replace this implementation with code to handle the error appropriately.
                    // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
                    
                    /*
                     Typical reasons for an error here include:
                     * The parent directory does not exist, cannot be created, or disallows writing.
                     * The persistent store is not accessible, due to permissions or data protection when the device is locked.
                     * The device is out of space.
                     * The store could not be migrated to the current model version.
                     Check the error message to determine what the actual problem was.
                     */
                    NSLog(@"Unresolved error %@, %@", error, error.userInfo);
                    abort();
                }
            }];
        }
    }
    
    return _persistentContainer;
}

-(NSManagedObjectContext *)managedObjectContext
{
    
    if (!_managedObjectContext) {
        _managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
        
        [_managedObjectContext setPersistentStoreCoordinator:self.persistentStoreCoordinator];
    }
    
    return _managedObjectContext;
}


-(NSManagedObjectModel *)managedObjectModel
{
    if (!_managedObjectModel) {
        _managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
    }
    return _managedObjectModel;
}

-(NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
    if (!_persistentStoreCoordinator) {
        
        _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel];
        //
        //
        //        NSDictionary *options =@{NSMigratePersistentStoresAutomaticallyOption:@(YES),
        //                                 NSInferMappingModelAutomaticallyOption:@(YES)
        //                                 };
        //        [_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:url options:options error:nil];
        
        BOOL useMigrationManager = NO;
        if (useMigrationManager &&
            [self isMigrationNecessaryForStore:DBURL])
        {
            [self performBackgroundManagedMigrationForStore:DBURL];
        }
        else
        {
            NSDictionary *options =
            @{
              NSMigratePersistentStoresAutomaticallyOption:@YES
              ,NSInferMappingModelAutomaticallyOption:@YES
              };
            NSError *error = nil;
            [_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
                                                      configuration:nil
                                                                URL:DBURL
                                                            options:options
                                                              error:&error];
        }
        
    }
    return _persistentStoreCoordinator;
}


- (BOOL)isMigrationNecessaryForStore:(NSURL*)storeUrl
{
    if (![[NSFileManager defaultManager] fileExistsAtPath:DBURL.path])
    {
        NSLog(@"SKIPPED MIGRATION: Source database missing.");
        return NO;
    }
    
    NSError *error = nil;
    NSDictionary *sourceMetadata =
    [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:NSSQLiteStoreType
                                                               URL:storeUrl error:&error];
    NSManagedObjectModel *destinationModel = _persistentStoreCoordinator.managedObjectModel;
    
    if ([destinationModel isConfiguration:nil compatibleWithStoreMetadata:sourceMetadata])
    {
        NSLog(@"SKIPPED MIGRATION: Source is already compatible");
        return NO;
    }
    
    return YES;
}


- (void)performBackgroundManagedMigrationForStore:(NSURL*)storeURL
{
    NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    
    dispatch_async(
                   dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
                       BOOL done = [self migrateStore:storeURL];
                       if(done) {
                           dispatch_async(dispatch_get_main_queue(), ^{
                               NSError *error = nil;
                               [self->_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
                                                                               configuration:nil
                                                                                         URL:DBURL
                                                                                     options:nil
                                                                                       error:&error];
                           });
                       }
                   });
}

- (BOOL)migrateStore:(NSURL*)sourceStore {
    
    NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    BOOL success = NO;
    NSError *error = nil;
    
    // STEP 1 - 收集 Source源实体, Destination目标实体 和 Mapping Model文件
    NSDictionary *sourceMetadata = [NSPersistentStoreCoordinator
                                    metadataForPersistentStoreOfType:NSSQLiteStoreType
                                    URL:sourceStore
                                    error:&error];
    
    NSManagedObjectModel *sourceModel =
    [NSManagedObjectModel mergedModelFromBundles:nil
                                forStoreMetadata:sourceMetadata];
    
    NSManagedObjectModel *destinModel = self.managedObjectModel;
    
    NSMappingModel *mappingModel =
    [NSMappingModel mappingModelFromBundles:nil
                             forSourceModel:sourceModel
                           destinationModel:destinModel];
    
    // STEP 2 - 开始执行 migration合并, 前提是 mapping model 不是空，或者存在
    if (mappingModel) {
        NSError *error = nil;
        NSMigrationManager *migrationManager =
        [[NSMigrationManager alloc] initWithSourceModel:sourceModel
                                       destinationModel:destinModel];
        //        [migrationManager addObserver:self
        //                           forKeyPath:@"migrationProgress"
        //                              options:NSKeyValueObservingOptionNew
        //                              context:NULL];
        
        NSURL *destinStore = DBURL;
        
        success =
        [migrationManager migrateStoreFromURL:sourceStore
                                         type:NSSQLiteStoreType options:nil
                             withMappingModel:mappingModel
                             toDestinationURL:destinStore
                              destinationType:NSSQLiteStoreType
                           destinationOptions:nil
                                        error:&error];
        if (success)
        {
            // STEP 3 - 用新的migrated store替换老的store
            
        }
        else
        {
            NSLog(@"FAILED MIGRATION: %@",error);
        }
    }
    else
    {
        NSLog(@"FAILED MIGRATION: Mapping Model is null");
    }
    
    return YES; // migration已经完成
}

@end
